/**
 * \file nfa.c
 **/

#include "nfa.h"
#include "mem.h"
#include "log.h"

void r_nfanode_add_child(NFANode* node, NFANode* target)
{
  assert(node->type == R_NFA_NODE_SPLIT);
  r_debug(R_DETAILED, "type = %d, target = %p", target->type, target);
  r_vector_append(&(node->nfa_split.splits), &target);
}

void r_nfanode_add_class(NFANode* node, NFAMatchClassType type,
                          uni_char extra1, uni_char extra2)
{
  assert(node->type == R_NFA_NODE_CHAR or
         node->type == R_NFA_NODE_NOT_CHAR);
  NFAMatchingClass match_class = {type, extra1, extra2};
  if (type == R_NFA_CLASS_RANGE){
    /* Ensure extra1 is the lower limit on the range. */
    match_class.extra1 = MIN(extra1, extra2);
    match_class.extra2 = MAX(extra1, extra2);
  }
  r_debug(R_DETAILED, "type = %d, extra1 = %d, extra2 = %d", type, extra1, extra2);
  r_vector_append(&(node->nfa_char.match_classes), &match_class);
}

void r_nfanode_init_char(NFANode* node, int num_classes)
{
  node->type = R_NFA_NODE_CHAR;
  node->nfa_char.match_classes.el_size = sizeof(NFAMatchingClass);
  r_vector_init_sized(&(node->nfa_char.match_classes), num_classes);
  node->extra = NULL;
  r_debug(R_DETAILED, "created char node %p", node);
}

void r_nfanode_init_split(NFANode* node, int num_splits)
{
  node->type = R_NFA_NODE_SPLIT;
  node->nfa_split.splits.el_size = sizeof(NFANode*);
  r_vector_init_sized(&(node->nfa_split.splits), num_splits);
  node->extra = NULL;
  r_debug(R_DETAILED, "created split node %p", node);
}

void r_nfanode_init_term(NFANode* node, int id)
{
  node->type = R_NFA_NODE_TERM;
  node->nfa_term.id = id;
  node->extra = NULL;
  r_debug(R_DETAILED, "created term node %p", node);
}

void r_nfastate_activate(NFAState* state, NFANode* node)
{
  /* If the node is a split, recursively activate it's children. */
  if (node->type == R_NFA_NODE_SPLIT) {
    int i;
    for (i = 0; i < node->nfa_split.splits.size; i++) {
      NFANode* target;
      r_vector_get_into(&(node->nfa_split.splits), i, &target);
      r_nfastate_activate(state, target);
    }
    return;
  }

  /* Append the node to the list of active nodes. */
  r_vector_append(&(state->active_states), &node);
  r_debug(R_DETAILED, "activating node %p", node);
}

/* R_NFA_START_STATES indicates how many simultaneous states are allowed in
 * the NFA after constructing NFAState. If more states are needed, additional
 * memory will be added. */
#define R_NFA_START_STATES 4
void r_nfastate_init(NFAState* state)
{
  state->active_states.el_size = sizeof(NFANode*);
  r_vector_init_sized(&(state->active_states), R_NFA_START_STATES);
}

void r_nfastate_step(NFAState* state, uni_char event)
{
  if (not r_nfastate_any(state)) {
    r_debug(R_DEBUG, "received event %d but no nodes active", event);
    return;
  }
  NFANode** active = state->active_states.data;
  uint32_t num_active = state->active_states.size;
  /* Make a temporary copy of the nodes that are active. */
  NFANode** tmpnodes = r_mem_alloca(sizeof(NFANode*) * num_active);
  int i;
  for (i = 0; i < num_active; i++) tmpnodes[i] = active[i];

  /* Now make no nodes active */
  r_vector_clear_lazy(&(state->active_states));
  r_debug(R_DEBUG, "stepping with event %d at %d active nodes", event,
                                                                  num_active);

  /* Go through the list of nodes that were active, and pass the uni_char
   * "event" to each of the Char nodes. (Term nodes are ignored.) */
  for (i = 0; i < num_active; i++) {
    NFANodeType type = tmpnodes[i]->type;
    if ((type == R_NFA_NODE_CHAR) or (type == R_NFA_NODE_NOT_CHAR)) {
      /* Now, check if the event is matched by this node. */
      int j; bool matched;
      if (type == R_NFA_NODE_CHAR)
        matched = false;
      else
        matched = true;

      for (j = 0; j < tmpnodes[i]->nfa_char.match_classes.size; j++){
        NFAMatchingClass* matching_class = (NFAMatchingClass*)
          r_vector_get(&(tmpnodes[i]->nfa_char.match_classes), j);
        switch (matching_class->type) {
          case R_NFA_CLASS_ANY:
            if (type == R_NFA_NODE_CHAR)
              matched = true;
            else
              matched = false;
            break;
          case R_NFA_CLASS_SINGLE:
            if (event == matching_class->extra1){
              if (type == R_NFA_NODE_CHAR)
                matched = true;
              else
                matched = false;
            }
            break;
          case R_NFA_CLASS_RANGE:
            if ((event <= matching_class->extra2) and
                (event >= matching_class->extra1)){
              if (type == R_NFA_NODE_CHAR)
                matched = true;
              else
                matched = false;
              }
            break;
          default:
            assert_never();
        }

        if (matched == true){
          r_nfastate_activate(state, tmpnodes[i]->nfa_char.next);
          break;
        }
      } /* end for all matching classes */
    } /* end if node is char or not char */
  } /* end for all previously active nodes */
}
